#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <termios.h>
#include <pthread.h>
#include <stdint.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <time.h>

#define NIT_SHORT_NAMES
#include <nit/socket.h>

#include "tr.h"
#include "tr-bind.h"

enum { ERROR, OK, QUIT };

pthread_mutex_t action = PTHREAD_MUTEX_INITIALIZER;
double sps = 0.2;
int state = OK;

static void
change_state(int new_state)
{
	pthread_mutex_lock(&action);
	state = new_state;
	pthread_mutex_unlock(&action);
}

static void
updater_end(int new_state, Blu_trad *trad)
{
	change_state(new_state);
	blu_trad_release(trad);
	pthread_exit(NULL);
}

static void
sps_update(Blu_trad *trad)
{
	pthread_mutex_lock(&action);
	sps = *(double *) trad->buf;
	pthread_mutex_unlock(&action);
}

static double
get_sps(void)
{
	double tmp;

	pthread_mutex_lock(&action);
        tmp = state;
	pthread_mutex_unlock(&action);
	return tmp;

}

static int
get_state(void)
{
	int tmp;

	pthread_mutex_lock(&action);
        tmp = state;
	pthread_mutex_unlock(&action);
	return tmp;
}

static void *
updater(void *args)
{
	Nit_joint *daemon = args;
	Blu_trad trad;

	blu_trad_init(&trad, sizeof(sps));

	while (1) {
		int32_t msg_size;
		int val;

		if (get_state() != OK) {
			blu_trad_release(&trad);
			pthread_exit(NULL);
		}

		switch (blu_tr(daemon, &trad)) {
		case NIT_JOIN_NONE:
			break;
		case NIT_JOIN_CLOSED:
			if (get_state() != ERROR)
				updater_end(QUIT, &trad);

			updater_end(ERROR, &trad);
		case NIT_JOIN_ERROR:
			updater_end(ERROR, &trad);
		case NIT_JOIN_OK:
			if (trad.msg != sizeof(sps))
			        updater_end(QUIT, &trad);

			sps_update(&trad);
			break;
		}
	}

	pthread_exit(NULL);
}

static int
term_set(void)
{
	struct termios settings;

	if (tcgetattr(STDIN_FILENO, &settings) < 0) {
		perror("tcsetattr");
		return 0;
	}

	/* turns off canonical mode so we don't go line by line */
	settings.c_lflag &= ~ICANON;

	/* blocks read until we have atleast one byte */
        settings.c_cc[VMIN] = 0;
	settings.c_cc[VTIME] = 0;

	if (tcsetattr(STDIN_FILENO, TCSANOW, &settings) < 0) {
		perror("tcsetattr");
		return 0;
	}

	return 1;
}

int
blu_tr_bind(const char *name)
{
	time_t timer;
	size_t size = 0;
	char buf[1024];
	pthread_t update_thread;
	Nit_joint *daemon = joint_connect(name);

	if (!daemon)
		return 0;

	if (!term_set())
		return 0;

	if (pthread_create(&update_thread, NULL, updater, daemon))
		return 0;

	time(&timer);

	while (1) {
		int read_val;
		time_t now;

		if ((read_val = read(STDIN_FILENO, buf + size, 1)) < 0) {
		        change_state(ERROR);
		        break;
		} else if (read_val) {
			++size;
		}

		if (!size) {
			continue;
		}

		if (get_state() != OK)
			break;

		time(&now);

		if (difftime(now, timer) > get_sps()) {
			if (!joint_send(daemon, buf, size)) {
				change_state(ERROR);
				break;
			}

			time(&timer);
			size = 0;
		}
	}

	pthread_join(update_thread, NULL);
	joint_free(daemon);
	return get_state() == QUIT;
}
